package com.iflytek.demo.ability.iat;


import com.iflytek.demo.ability.AppLog;
import com.iflytek.demo.ability.Tools;
import ohos.utils.zson.ZSONObject;
import okhttp3.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 讯飞开放平台 听写客户端 【 语音听写（流式版）WebAPI 】
 * https://www.xfyun.cn/doc/asr/voicedictation/API.html
 *
 * @author zhhuang10
 * @date 2021/7/2
 */
public class IatClient {
    // 服务器地址 【固定的】
    private final String mHostUrl = "wss://iat-api.xfyun.cn/v2/iat";
    private final String mHost = "iat-api.xfyun.cn";
    private final String mPath = "/v2/iat";

    // 账号信息,需要从开放平台注册后获取
    private String mAppId;
    private String mApiSecret;
    private String mApiKey;

    // 语言参数
    private String mLang;
    // 口音
    private String mAccent;
    // WebSocket
    private WebSocket mWebSocket;
    // CallBack
    private CallBack mCallBack;
    /**
     * Lock
     */
    private final Lock mLock = new ReentrantLock();
    /**
     * Condition
     */
    private final Condition condition = mLock.newCondition();
    /**
     * 录音机器
     */
    private AudioRecorder mAudioRecorder;
    /**
     * WebSocket 连接是否成功标识
     */
    private volatile boolean mWebSocketSuccess;

    public IatClient(Builder builder) {
        mAppId = builder.appId;
        mApiSecret = builder.apiSecret;
        mApiKey = builder.apiKey;
        mLang = builder.lang;
        mAccent = builder.accent;
    }

    public final void setCallBack(IatClient.CallBack callBack) {
        this.mCallBack = callBack;
    }

    /**
     * 开始录音并识别,并将识别结果通过callBack返回
     */
    public void startListening() {
        new Thread(() -> {
            boolean success = start();
            if (!success) {
                return;
            }
            if (mCallBack != null) {
                mCallBack.onStart();
            }
            if (mAudioRecorder == null) {
                mAudioRecorder = new AudioRecorder();
                mAudioRecorder.initConfig();
            }
            mAudioRecorder.setFinishCallBack(this::sendEnd);
            mAudioRecorder.startRecord(this::sendData);
        }).start();
    }

    /**
     * 停止录音
     */
    public void stopListening() {
        if (mAudioRecorder != null) {
            mAudioRecorder.stopRecord();
            mAudioRecorder.release();
            mAudioRecorder = null;
        }
    }

    /**
     * cancel
     */
    public final void cancel() {
        stopListening();
        if (mWebSocket != null) {
            mWebSocket.close(1000, "cancel");
        }
        mCallBack = null;
    }

    /**
     * 开始识别
     */
    public final boolean start() {
        // 先取消上次会话
        if (mWebSocket != null) {
            mWebSocket.cancel();
            mWebSocket = null;
        }
        // 取消上次录音
        stopListening();
        // 构建鉴权url
        final String authUrl = createAuthUrl(mApiKey, mApiSecret);
        final OkHttpClient client = Tools.okHttp();
        final Request request = new Request.Builder().url(authUrl).build();
        mWebSocketSuccess = false;
        mWebSocket = client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                sendFirst();
                mWebSocketSuccess = true;
                conditionSignalAll();
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {

                AppLog.e("iat", "onMessage  text = \n$text");
                IatResponseData resp = null;
                try {
                    resp = ZSONObject.stringToClass(text, IatResponseData.class);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                if (resp == null || resp.code != 0) {
                    return;
                }
                // resp.data.status == 2 说明数据全部返回完毕，可以关闭连接，释放资源
                if (resp.data != null && resp.data.status == 2) {
                    stopListening();
                    webSocket.close(1000, "ok");
                }
                if (mCallBack != null && resp.data != null && resp.data.result != null) {
                    mCallBack.onResult(resp.data.result.getWorld(), resp.data.status == 2);
                }
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                mWebSocket = null;
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                if (mCallBack == null) {
                    return;
                }
                if (response != null) {
                    mCallBack.onError(response.code());
                } else {
                    mCallBack.onError(1);
                }
                mWebSocketSuccess = false;
                conditionSignalAll();
            }
        });
        lockAwait();
        return mWebSocketSuccess;
    }

    /**
     * 线程等待
     */
    private void lockAwait() {
        mLock.lock();
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }
    }

    /**
     * 线程唤醒
     */
    private void conditionSignalAll() {
        mLock.lock();
        condition.signalAll();
        mLock.unlock();
    }

    /**
     * 第一帧
     */
    public final void sendFirst() {
        ZSONObject frame = new ZSONObject();
        ZSONObject business = new ZSONObject();
        ZSONObject common = new ZSONObject();
        ZSONObject data = new ZSONObject();
        common.put("app_id", this.mAppId);
        business.put("domain", "iat");
        business.put("language", this.mLang);
        business.put("accent", this.mAccent);
        business.put("nunum", 0);
        business.put("ptt", 1);
        data.put("status", 0);
        data.put("format", "audio/L16;rate=16000");
        data.put("encoding", "raw");
        data.put("audio", "");
        frame.put("common", common);
        frame.put("business", business);
        frame.put("data", data);
        if (mWebSocket != null) {
            mWebSocket.send(frame.toString());
        }

    }

    /**
     * 发送音频
     *
     * @param byteArray 音频
     */
    public final void sendData(byte[] byteArray) {
        ZSONObject frame1 = new ZSONObject();
        ZSONObject data1 = new ZSONObject();
        data1.put("status", 1);
        data1.put("format", "audio/L16;rate=16000");
        data1.put("encoding", "raw");
        data1.put("audio", Tools.base64(byteArray));
        frame1.put("data", data1);
        if (mWebSocket != null) {
            mWebSocket.send(frame1.toString());
        }
    }

    /**
     * 发送最后一帧数据
     */
    public final void sendEnd() {
        ZSONObject frame2 = new ZSONObject();
        ZSONObject data2 = new ZSONObject();
        data2.put("status", 2);
        data2.put("audio", "");
        data2.put("format", "audio/L16;rate=16000");
        data2.put("encoding", "raw");
        frame2.put("data", data2);
        if (mWebSocket != null) {
            mWebSocket.send(frame2.toString());
        }
    }


    /**
     * 获取鉴权url
     */
    private String createAuthUrl(String apiKey, String apiSecret) {
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        String signatureOrigin = "host: " + this.mHost + "\ndate: " + date + "\nGET " + this.mPath + " HTTP/1.1";
        String sha = Tools.base64(Tools.hmacsha256(signatureOrigin.getBytes(), apiSecret.getBytes()));
        String authorization = "api_key=\"" + apiKey + "\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"" + sha + '"';
        String authBase64 = Tools.base64(authorization);
        return this.mHostUrl + "?authorization=" + authBase64 + "&date=" + date + "&host=" + this.mHost;
    }

    /**
     * Builder
     */
    public static class Builder {
        // 账号
        private String appId;
        private String apiSecret;
        private String apiKey;
        // 识别语音的所属语言
        private String lang = "zh_cn";
        // 口音
        private String accent = "mandarin";

        public IatClient build() {
            return new IatClient(this);
        }

        public Builder setAppId(String appId) {
            this.appId = appId;
            return this;
        }

        public Builder setApiSecret(String apiSecret) {
            this.apiSecret = apiSecret;
            return this;
        }

        public Builder setApiKey(String apiKey) {
            this.apiKey = apiKey;
            return this;
        }

        public Builder setLang(String lang) {
            this.lang = lang;
            return this;
        }

        public Builder setAccent(String accent) {
            this.accent = accent;
            return this;
        }
    }

    /**
     * 回调
     */
    public interface CallBack {
        /**
         * 已经连接服务端 开始传送音频，录音机开启，此时说话才会被收录
         */
        void onStart();

        /**
         * 识别结果
         *
         * @param str        分段返回的结果
         * @param isComplete 是否已经全部返回
         */
        void onResult(String str, boolean isComplete);

        /**
         * onError
         */
        void onError(int code);
    }

    /**
     * 结果解析bean
     */
    public static class IatResponseData {
        public int code = 0;
        public String message;
        public String sid;
        public IatData data;

    }

    public static class IatData {
        public int status = 0;
        public String message;
        public String sid;
        public IatResultBean result;
    }

    public static class IatResultBean {
        // ws
        public List<WsBean> ws;

        /**
         * 解析识别结果
         */
        public String getWorld() {
            StringBuilder sb = new StringBuilder();
            if (ws != null) {
                for (WsBean it : ws) {
                    if (it.cw != null) {
                        for (CwBean cwBean : it.cw) {
                            if (cwBean.w != null) {
                                sb.append(cwBean.w);
                            }
                        }
                    }
                }
            }
            return sb.toString();
        }
    }

    public static class WsBean {
        public List<CwBean> cw;
    }

    public static class CwBean {
        public String w;
    }
}